/**
 * Copyright (c) 2010 Andrey Somov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.yaml.scalayaml.reader

import java.io._

import org.yaml.scalayaml.scanner.Constant
import org.yaml.scalayaml.error.{Mark, YAMLException}

/**
 * CamelReader: checks if characters are in allowed range, adds '\0' to the end.
 * 
 * @see <a href="http://pyyaml.org/wiki/PyYAML">PyYAML</a> for more information
 */
trait StreamReader {
    private var name: String = "<stream>"
    def read(data: Array[Char]): Int
    private var pointer = 0
    protected var eof = false
    protected var buffer = new StringBuilder()
    var index = 0
    var line = 0
    var column = 0
    
    protected def readString(yaml: String) {
        name = "<string>"
        checkPrintable(yaml)
        buffer.append(yaml)
        eof = true
    }
    
    def peek(index:Int = 0):Char = {
        if (pointer + index + 1 > buffer.length) {
            update(index + 1)
        }
        buffer(pointer + index)
    }
    
    def prefix(length:Int = 1) = {
        if (pointer + length >= buffer.length) {
            update(length);
        }
        buffer.slice(pointer, pointer + length).toString
    }
    
    def forward(length:Int = 1) {
        if (pointer + length + 1 >= buffer.length) {
            update(length + 1);
        }
        var i = length
        while (i > 0) {
            var ch = buffer(pointer)
            pointer += 1
            index += 1
            if (Constant.LINEBR.has(ch) || (ch == '\r' && buffer(pointer) != '\n')) {
                line += 1
                column = 0
            } else if (ch != '\uFEFF') {
                column += 1
            }
            i -= 1
        }
    }
    
    def getMark() = {
        new Mark(name, index, line, column, buffer.toString, pointer)
    }
    
    def checkPrintable(data: CharSequence) {
        CamelReader.NON_PRINTABLE.findFirstMatchIn(data) match {
            case Some(em)  => {
                val position = index + buffer.length - pointer + em.start
                throw new ReaderException(name, position, em.group(0).charAt(0),
                        " special characters are not allowed")
            }
            case None => 
        }
    }
    
    private def update(length: Int) {
        buffer.delete(0, pointer)
        pointer = 0
        var proceed = true
        while (proceed && buffer.length < length) {
            var rawData:String = null
            if (!eof) {
                var data = Array[Char](1024)
                var converted = -2
                try {
                    converted = read(data)
                } catch {
                    case ioe:IOException => throw new YAMLException(ioe)
                }
                if (converted == -1) {
                    eof = true
                } else {
                    rawData = new String(data, 0, converted)
                }
            }
            if (rawData != null) {
                checkPrintable(rawData)
                buffer.append(rawData)
            }
            if (eof) {
                buffer.append('\0')
                proceed = false
            }
        }
    }
}

object CamelReader {
    val NON_PRINTABLE = "[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFC]".r
}

import org.yaml.scalayaml.error.YAMLException

class ReaderException (name: String, position: Int, ch: Char, text: String) extends YAMLException(text) {
    override def toString() = {
        "unacceptable character #" + Integer.toHexString(ch.toInt).toUpperCase() + " " + 
        getMessage() + "\nin \"" + name + "\", position " + position;
    }
}
